/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.codestd.security.shiro.filter;

import com.codestd.security.shiro.ResponseUtils;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.authz.permission.WildcardPermissionResolver;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义角色权限过滤器，auth[PERM_perm001,ROLE_admin]
 *
 * @author Wang Chengwei(Jaune)
 * @since 1.0.0
 */
public class RolePermissionAuthorizationFilter extends AuthorizationFilter {

    // 增加权限和角色前缀的配置，好让我们能够从前缀区分权限和角色，从而调用对应的方法去做验证
    static final String DEFAULT_PERMISSION_PREFIX = "PERM_";
    static final String DEFAULT_ROLE_PREFIX = "ROLE_";

    // 启用严格模式
    private boolean strict;

    public void setStrict(boolean strict) {
        this.strict = strict;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        RolePermissionSegregator sgregator = new RolePermissionSegregator(DEFAULT_PERMISSION_PREFIX, DEFAULT_ROLE_PREFIX, mappedValue);
        if (sgregator.isEmpty()) {
            return true;
        }
        Subject subject = getSubject(request, response);
        if (strict) {
            return strictCheck(subject, sgregator);
        } else {
            for (String role : sgregator.getRoles()) {
                if (subject.hasRole(role)) {
                    return true;
                }
            }

            for (Permission permission : sgregator.getPermissions()) {
                if (subject.isPermitted(permission)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {
        ResponseUtils.printAccessDeniedError(response);
        return false;
    }

    private boolean strictCheck(Subject subject, RolePermissionSegregator sgregator) {
        if (!sgregator.getPermissions().isEmpty()) {
            if (sgregator.getPermissions().size() == 1) {
                if (!subject.isPermitted(sgregator.getPermissions().get(0))) {
                    return false;
                }
            } else {
                if (!subject.isPermittedAll(sgregator.getPermissions())) {
                    return false;
                }
            }
        }

        // 这里可以直接return，因为后面没有条件判断了
        if (!sgregator.getRoles().isEmpty()) {
            if (sgregator.getRoles().size() == 1) {
                return subject.hasRole(sgregator.getRoles().get(0));
            } else {
                return subject.hasAllRoles(sgregator.getRoles());
            }
        }
        return true;
    }

    /**
     * 处理角色和权限，按照前缀将角色和权限分组
     */
    private static class RolePermissionSegregator {
        private List<String> roles = new ArrayList<>();
        private List<Permission> permissions = new ArrayList<>();
        private PermissionResolver permissionResolver = new WildcardPermissionResolver();

        private String permissionPrefix;
        private String rolePrefix;

        RolePermissionSegregator(String permissionPrefix, String rolePrefix, Object mappedValue) {
            this.permissionPrefix = permissionPrefix;
            this.rolePrefix = rolePrefix;
            this.init(mappedValue);
        }

        List<String> getRoles() {
            return roles;
        }

        List<Permission> getPermissions() {
            return permissions;
        }

        boolean isEmpty() {
            return this.roles.isEmpty() && this.permissions.isEmpty();
        }

        private void init(Object mappedValue) {
            String[] perms = (String[]) mappedValue;
            for (String perm : perms) {
                if (perm.startsWith(this.rolePrefix)) {
                    this.roles.add(perm.replace(this.rolePrefix, ""));
                } else if (perm.startsWith(this.permissionPrefix)) {
                    Permission permission = this.permissionResolver.resolvePermission(perm.replace(this.permissionPrefix, ""));
                    this.permissions.add(permission);
                }
            }
        }
    }
}
